﻿using System;
using Microsoft.Xna.Framework;

namespace Grafica1
{
    public class Camera
    {

        #region Consts
        public const float NEAR_PLANE = 1f;
        public const float FAR_PLANE = 300f;
        public const float DEFAULT_FIELD_OF_VIEW = MathHelper.PiOver4;
        public const float DEFAULT_ASPECT_RATIO = 1.33333333f;
        #endregion

        #region private variables
        private string _name = "Default Camera";
        private Vector3 _position = Vector3.Zero;
        private Quaternion _orientation = new Quaternion(0, 0, 1, 0);
        private float _fieldOfView = DEFAULT_FIELD_OF_VIEW;
        private float _aspectRatio = DEFAULT_ASPECT_RATIO;
        private float _nearPlane = NEAR_PLANE;
        private float _farPlane = FAR_PLANE;
        private Matrix _View;
        private Matrix _Projection;
        private BoundingFrustum _BoundingFrustum;


        #endregion

        #region Properties
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        public Vector3 position
        {
            get
            {
                return _position;
            }
            set
            {
                _position = value;
                recalcView();
            }
        }
        public Quaternion orientation
        {
            get
            {
                return _orientation;
            }
            set
            {
                _orientation = value;
                recalcView();
            }
        }
        public float fieldOfView
        {
            get
            {
                return _fieldOfView;
            }
            set
            {
                _fieldOfView = value;
                recalcProjection();
            }
        }
        public float AspectRatio
        {
            get
            {
                return _aspectRatio;
            }
            set
            {
                _aspectRatio = value;
                recalcProjection();
            }
        }
        public float NearPlane
        {
            get
            {
                return _nearPlane;
            }
            set
            {
                _nearPlane = value;
                recalcProjection();
            }
        }
        public float FarPlane
        {
            get
            {
                return _farPlane;
            }
            set
            {
                _farPlane = value;
                recalcProjection();
            }
        }
        public Matrix View
        {
            get
            {
                return _View;
            }
            set
            {
                _View = value;
                rePosOri();
                recalcBoundingFrustum();
            }
        }
        public Matrix Projection
        {
            get
            {
                return _Projection;
            }
            set { }
        }
        public BoundingFrustum BoundingFrustum
        {
            get { return _BoundingFrustum; }
            private set { }
        }
        #endregion

        #region Constructors

        public Camera(Vector3 Position, Quaternion Orientation, float FieldOfView, float AspectRatio)
        {
            _position = Position;
            _orientation = Orientation;
            recalcView();
            _fieldOfView = FieldOfView;
            _aspectRatio = AspectRatio;
            recalcProjection();
            recalcBoundingFrustum();
        }

        private Camera()
        {
            recalcView();
            recalcProjection();
            recalcBoundingFrustum();
        }

        #endregion

        #region public methods

        public override string ToString()
        {
            var lookTo = Matrix.CreateFromQuaternion(_orientation) * Matrix.CreateTranslation(_position);

            return
                "Camera " + _name + "\n" +
                "position = " + _position + "\n" +
                "looking to = " + lookTo.Forward + "\n" +
                "up = " + lookTo.Up + "\n" +
                "orientation = " + _orientation + "\n" +
                "field of view = " + _fieldOfView + "\n" +
                "aspect ratio = " + _aspectRatio + "\n" +
                "near plane = " + _nearPlane + "\n" +
                "far plane = " + _farPlane + "\n" +
                "View = \n" + toNiceString(_View) + "\n" +
                "Projection = \n" + toNiceString(_Projection) + "\n";
        }


        #endregion

        #region private methods

        //calculate th matrix view and Projection
        private void recalcProjection()
        {
            _Projection = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, _aspectRatio, _nearPlane, _farPlane);
            recalcBoundingFrustum();
        }
        private void recalcView()
        {
            var cameraWorld = Matrix.CreateFromQuaternion(_orientation);
            cameraWorld.Translation = _position;
            _View = Matrix.Invert(cameraWorld);
            recalcBoundingFrustum();
        }
        private void recalcBoundingFrustum()
        {
            _BoundingFrustum = new BoundingFrustum(_View * _Projection);
        }
        private void rePosOri()
        {
            _position = Vector3.Transform(-_View.Translation, Matrix.Transpose(_View));
            Vector3 a;
            Matrix.Transpose(_View).Decompose(out a, out _orientation, out a);
        }

        #endregion

        #region static variables
        //returns a instance of the default camera 
        public static Camera DefaultCamera = new Camera();
        #endregion

        #region static methods
        //returns pretty print of a matrix
        public static String toNiceString(Matrix m)
        {
            var pass = m.ToString().Replace("} {", "\n");
            pass = pass.Replace("{ {", "");
            pass = pass.Replace("} }", "");
            for (int c = 1; c < 5; c++)
            {
                for (int i = 1; i < 5; i++)
                {
                    pass = pass.Replace("M" + i + c + ":", "");
                }
            }

            var passes = pass.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);

            var ret = "";

            foreach (var p in passes)
            {
                var s = p.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                var rowFormat = "";
                foreach (var n in s)
                {
                    var gen = " ";
                    for (int i = 0; i < 9 - n.Length; i++)
                    {
                        gen += " ";
                    }
                    rowFormat += n + gen + "|";
                }
                ret += rowFormat + "\n";
            }

            return ret;
        }
        #endregion

    }
}
